﻿using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Vanara.PInvoke;
using static Vanara.PInvoke.Shell32;

namespace Vanara.Windows.Shell;

/// <summary>The Shell's progress dialog.</summary>
/// <seealso cref="Component"/>
public class ShellFileOperationDialog : Component
{
	internal IOperationsProgressDialog? iProgressDialog;

	private ShellItem? currentItem;
	private long currentItems;
	private long currentProgress;
	private long currentSize;
	[MaybeNull]
	private ShellItem destItem, sourceItem;
	private OperationMode mode;
	private OperationType operation;
	private long totalItems = 100;
	private long totalProgress = 100;
	private long totalSize = 100;

	/// <summary>Initializes a new instance of the <see cref="ShellFileOperationDialog"/> class.</summary>
	public ShellFileOperationDialog()
	{
	}

	/// <summary>Initializes a new instance of the <see cref="ShellFileOperationDialog"/> class.</summary>
	/// <param name="container">The container.</param>
	public ShellFileOperationDialog(IContainer container) : this() => container.Add(this);

	/// <summary>Provides operation status flags for ShellFileOperationDialog.</summary>
	public enum DialogStatus
	{
		/// <summary>The dialog has not been started.</summary>
		NotStarted = 0,

		/// <summary>Operation is running, no user intervention.</summary>
		Running = PDOPSTATUS.PDOPS_RUNNING,

		/// <summary>Operation has been paused by the user.</summary>
		Paused = PDOPSTATUS.PDOPS_PAUSED,

		/// <summary>Operation has been canceled by the user - now go undo.</summary>
		Cancelled = PDOPSTATUS.PDOPS_CANCELLED,

		/// <summary>Operation has been stopped by the user - terminate completely.</summary>
		Stopped = PDOPSTATUS.PDOPS_STOPPED,

		/// <summary>Operation has gone as far as it can go without throwing error dialogs.</summary>
		Errors = PDOPSTATUS.PDOPS_ERRORS,
	}

	/// <summary>Flags used in Mode</summary>
	[Flags]
	public enum OperationMode
	{
		/// <summary>Use the default progress dialog operations mode.</summary>
		Default = PDMODE.PDM_DEFAULT,

		/// <summary>The operation is running.</summary>
		Running = PDMODE.PDM_RUN,

		/// <summary>The operation is gathering data before it begins, such as calculating the predicted operation time.</summary>
		Starting = PDMODE.PDM_PREFLIGHT,

		/// <summary>The operation is rolling back due to an Undo command from the user.</summary>
		Undoing = PDMODE.PDM_UNDOING,

		/// <summary>Error dialogs are blocking progress from continuing.</summary>
		BlockedByErrors = PDMODE.PDM_ERRORSBLOCKING,

		/// <summary>The length of the operation is indeterminate. Do not show a timer and display the progress bar in marquee mode.</summary>
		Indeterminate = PDMODE.PDM_INDETERMINATE,
	}

	/// <summary>Describes an action being performed that requires progress to be shown to the user using progress dialog.</summary>
	public enum OperationType
	{
		/// <summary>No action is being performed.</summary>
		None = SPACTION.SPACTION_NONE,

		/// <summary>Files are being moved.</summary>
		Moving = SPACTION.SPACTION_MOVING,

		/// <summary>Files are being copied.</summary>
		Copying = SPACTION.SPACTION_COPYING,

		/// <summary>Files are being deleted.</summary>
		Recycling = SPACTION.SPACTION_RECYCLING,

		/// <summary>A set of attributes are being applied to files.</summary>
		ApplyingAttributes = SPACTION.SPACTION_APPLYINGATTRIBS,

		/// <summary>A file is being downloaded from a remote source.</summary>
		Downloading = SPACTION.SPACTION_DOWNLOADING,

		/// <summary>An Internet search is being performed.</summary>
		SearchingInternet = SPACTION.SPACTION_SEARCHING_INTERNET,

		/// <summary>A calculation is being performed.</summary>
		Calculating = SPACTION.SPACTION_CALCULATING,

		/// <summary>A file is being uploaded to a remote source.</summary>
		Uploading = SPACTION.SPACTION_UPLOADING,

		/// <summary>A local search is being performed.</summary>
		SearchingFiles = SPACTION.SPACTION_SEARCHING_FILES,

		/// <summary>Windows Vista and later. A deletion is being performed.</summary>
		Deleting = SPACTION.SPACTION_DELETING,

		/// <summary>Windows Vista and later. A renaming action is being performed.</summary>
		Renaming = SPACTION.SPACTION_RENAMING,

		/// <summary>Windows Vista and later. A formatting action is being performed.</summary>
		Formatting = SPACTION.SPACTION_FORMATTING,

		/// <summary>Windows 7 and later. A copy or move action is being performed.</summary>
		CopyMoving = SPACTION.SPACTION_COPY_MOVING,
	}

	/// <summary>The operation can be undone in the dialog. (The Stop button becomes Undo)</summary>
	[DefaultValue(false)]
	public bool AllowUndo { get; set; }

	/// <summary>
	/// A ShellItem that represents the item currently being operated on by the operation engine. This property is only used in Windows
	/// 7 and later. In earlier versions, this property should be <see langword="null"/>
	/// </summary>
	[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
	public ShellItem? CurrentItem { get => currentItem; set { currentItem = value; UpdateLocations(); } }

	/// <summary>A ShellItem that represents the target Shell item.</summary>
	[MaybeNull]
	[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
	public ShellItem Destination { get => destItem; set { destItem = value ?? throw new ArgumentNullException(nameof(Destination)); if (Source is not null) UpdateLocations(); } }

	/// <summary>Gets the elapsed time.</summary>
	/// <value>The elapsed time, accurate to milliseconds.</value>
	public TimeSpan ElapsedTime
	{
		get
		{
			ulong t = 0;
			if (CanProcess)
				try { iProgressDialog!.GetMilliseconds(out t, out _); } catch { }
			return TimeSpan.FromMilliseconds(t);
		}
	}

	/// <summary>Don't display the path of destination file in progress dialog</summary>
	[DefaultValue(false)]
	public bool HideDestinationPath { get; set; }

	/// <summary>Don't display the location line in the progress dialog</summary>
	[DefaultValue(false)]
	public bool HideLocations { get; set; }

	/// <summary>Don't display the path of source file in progress dialog</summary>
	[DefaultValue(false)]
	public bool HideSourcePath { get; set; }

	/// <summary>Gets or sets progress dialog operations mode.</summary>
	/// <value>The mode.</value>
	[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
	public OperationMode Mode
	{
		get => mode;
		set
		{
			if (mode == value) return;
			mode = value;
			if (CanProcess)
				iProgressDialog!.SetMode((PDMODE)mode);
		}
	}

	/// <summary>Sets which progress dialog operation is occurring, and whether we are in pre-flight or undo mode.</summary>
	/// <value>Specifies operation. See <see cref="OperationType"/>.</value>
	[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
	public OperationType Operation
	{
		get => operation;
		set
		{
			if (operation == value) return;
			operation = value;
			if (CanProcess)
				iProgressDialog!.SetOperation((SPACTION)operation);
		}
	}

	/// <summary>Total points, used for showing progress in points.</summary>
	[DefaultValue(100)]
	public long ProgressBarMaxValue
	{
		get => totalProgress;
		set { totalProgress = value; UpdateProgress(); }
	}

	/// <summary>Current points, used for showing progress in points.</summary>
	[DefaultValue(0)]
	public long ProgressBarValue
	{
		get => currentProgress;
		set { currentProgress = value; UpdateProgress(); }
	}

	/// <summary>Specifies total items, used for showing progress in items.</summary>
	[DefaultValue(100)]
	public long ProgressDialogItemsMaxValue
	{
		get => totalItems;
		set { totalItems = value; UpdateProgress(); }
	}

	/// <summary>Current items, used for showing progress in items.</summary>
	[DefaultValue(0)]
	public long ProgressDialogItemsValue
	{
		get => currentItems;
		set { currentItems = value; UpdateProgress(); }
	}

	/// <summary>Total size in bytes, used for showing progress in bytes.</summary>
	[DefaultValue(100)]
	public long ProgressDialogSizeMaxValue
	{
		get => totalSize;
		set { totalSize = value; UpdateProgress(); }
	}

	/// <summary>Current size in bytes, used for showing progress in bytes.</summary>
	[DefaultValue(0)]
	public long ProgressDialogSizeValue
	{
		get => currentSize;
		set { currentSize = value; UpdateProgress(); }
	}

	/// <summary>Gets the remaining time.</summary>
	/// <value>The remaining time, accurate to milliseconds.</value>
	public TimeSpan RemainingTime
	{
		get
		{
			ulong t = 0;
			if (CanProcess)
				try { iProgressDialog!.GetMilliseconds(out _, out t); } catch { }
			return TimeSpan.FromMilliseconds(t);
		}
	}

	/// <summary>Add a pause button (operation can be paused)</summary>
	[DefaultValue(false)]
	public bool ShowPauseButton { get; set; }

	/// <summary>A ShellItem that represents the source Shell item.</summary>
	[MaybeNull]
	[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
	public ShellItem Source { get => sourceItem; set { sourceItem = value ?? throw new ArgumentNullException(nameof(Source)); if (Destination is not null) UpdateLocations(); } }

	/// <summary>Gets operation status for progress dialog.</summary>
	/// <value>The operation status. See <see cref="DialogStatus"/>.</value>
	public DialogStatus Status => (DialogStatus)(CanProcess ? iProgressDialog!.GetOperationStatus() : 0);

	private bool CanProcess => iProgressDialog != null;

	private OPPROGDLGF DialogFlags => (ShowPauseButton ? OPPROGDLGF.OPPROGDLG_ENABLEPAUSE : 0) |
						(AllowUndo ? OPPROGDLGF.OPPROGDLG_ALLOWUNDO : 0) |
						(HideSourcePath ? OPPROGDLGF.OPPROGDLG_DONTDISPLAYSOURCEPATH : 0) |
						(HideDestinationPath ? OPPROGDLGF.OPPROGDLG_DONTDISPLAYDESTPATH : 0) |
						(HideLocations ? OPPROGDLGF.OPPROGDLG_DONTDISPLAYLOCATIONS : 0);

	/// <summary>Pauses progress dialog timer.</summary>
	public void PauseTimer() { if (CanProcess) iProgressDialog!.PauseTimer(); }

	/// <summary>Resets progress dialog timer to 0.</summary>
	public void ResetTimer() { if (CanProcess) iProgressDialog!.ResetTimer(); }

	/// <summary>Resumes progress dialog timer.</summary>
	public void ResumeTimer() { if (CanProcess) iProgressDialog!.ResumeTimer(); }

	/// <summary>Starts the specified progress dialog.</summary>
	/// <param name="owner">
	/// A value that represents the window of the owner window for the common dialog box. This value can be <see langword="null"/>.
	/// </param>
	public void Start(HWND owner = default)
	{
		iProgressDialog = new IOperationsProgressDialog();
		iProgressDialog.StartProgressDialog(owner, DialogFlags);
		iProgressDialog.SetOperation((SPACTION)operation);
		iProgressDialog.SetMode((PDMODE)mode);
		UpdateLocations();
		UpdateProgress();
	}

	/// <summary>Stops current progress dialog.</summary>
	public void Stop()
	{
		if (!CanProcess)
			return;

		iProgressDialog!.StopProgressDialog();
		Thread.Sleep(500);
		iProgressDialog = null;
	}

	/// <summary>
	/// Releases the unmanaged resources used by the <see cref="T:System.ComponentModel.Component"/> and optionally releases the managed resources.
	/// </summary>
	/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
	protected override void Dispose(bool disposing)
	{
		Stop();
		base.Dispose(disposing);
	}

	private void UpdateLocations()
	{
		if (sourceItem is null) throw new ArgumentNullException(nameof(Source), "Source must be set.");
		if (destItem is null) throw new ArgumentNullException(nameof(Destination), "Destination must be set.");
		if (CanProcess)
			iProgressDialog!.UpdateLocations(sourceItem.IShellItem, destItem.IShellItem, currentItem?.IShellItem);
	}

	private void UpdateProgress()
	{
		if (CanProcess)
			iProgressDialog!.UpdateProgress((ulong)currentProgress, (ulong)totalProgress, (ulong)currentSize, (ulong)totalSize, (ulong)currentItems, (ulong)totalItems);
	}
}